#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/input.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/time.h>
#include <linux/semaphore.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>

#include <asm/sizes.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#define KEY_IRQ_COUNT   6

struct s3c2440_key {
    void __iomem *GPIO_virt;
    unsigned long GPIO_phys;
    unsigned long GPFCON,GPFDAT,GPFUP,GPGCON,GPGDAT,GPGUP,LCD;
    
    unsigned int data;
    unsigned int refresh;
    volatile unsigned int interval;
    unsigned int kflag;
    unsigned int last_irq;
    unsigned int read_count;
    wait_queue_head_t q;
    struct cdev cdev;
    struct input_dev *input_dev;
    struct timer_list timer;
    struct fasync_struct *fa;
    struct semaphore sem;
    int irq[KEY_IRQ_COUNT];
};

struct s3c2440_key key;

int init_input(struct s3c2440_key *dev)
{
    dev->input_dev = input_allocate_device();
    if (! dev->input_dev)
    {
        printk(KERN_ERR "Unable to allocate the input device!!\n");
        return -ENOMEM;
    }
    dev->input_dev->evbit[0] = BIT(EV_KEY);
    dev->input_dev->keybit[KEY_ESC] = BIT(KEY_ESC);
   // key->dev->name = "key_input";
    
    input_register_device(dev->input_dev);

    return 0;
}

irqreturn_t key_handler_single(int irq, void *arg)
{
    struct s3c2440_key *dev = (struct s3c2440_key *)arg;

    dev->data = 0;
    if (IRQ_EINT0 == irq)
        dev->data = 1;
    else if (IRQ_EINT1 == irq)
        dev->data = 2;

    dev->refresh = 1;
    wake_up(&dev->q);

    return IRQ_HANDLED;
}

irqreturn_t key_handler(int irq, void *arg)
{
    struct s3c2440_key *dev = (struct s3c2440_key *)arg;
    static int flag = 1;
    static int tmp = 0;
    if (dev->interval)
    {
        tmp = 1;
        return IRQ_HANDLED;
    }
    if (0 == flag && dev->last_irq != irq)
    {
        return IRQ_HANDLED;
    }
    if (tmp && (dev->data < 10 && 0 == flag))
    {
        flag = 1;
    }
    tmp = 0;
    dev->data = 0;
    if (flag)
    {
        switch (irq)
        {
            case IRQ_EINT2:
                dev->data = 3;
                break;
            case IRQ_EINT3:
                dev->data = 4;
                break;
            case IRQ_EINT8:
                dev->data = 5;
                break;
            case IRQ_EINT9:    
                dev->data = 6;
                break;
            default:
                break;
        }
        flag = 0;
    }
    else
    {
        switch (irq)
        {
            case IRQ_EINT2:
                dev->data = 13;
                break;
            case IRQ_EINT3:
                dev->data = 14;
                break;
            case IRQ_EINT8:
                dev->data = 15;
                break;
            case IRQ_EINT9:    
                dev->data = 16;
                break;
            default:
                break;
        }
        flag = 1; 
    }    
    /*
    if (IRQ_EINT0 == irq)
    {
        //printk("KEY EINT0\n");
        dev->data = 1;        
      //  input_report_key(dev->input_dev, KEY_ESC, 27);
      //  input_sync(dev->input_dev);
    }
    else if (IRQ_EINT1 == irq)
    {
        //printk("KEY EINT1\n");
        dev->data = 2;
        //input_report_key(dev->input_dev, KEY_DOWN, 274);
        //input_sync(dev->input_dev);
    }
    */
    
    //dev->timer.expires = jiffies + 2;
    //printk("<kernel> ===  expires = %d, irq = %d \n", dev->timer.expires, dev->data);
    //add_timer(&dev->timer);
    mod_timer(&dev->timer, jiffies + 2);
    dev->interval = 1;

    dev->last_irq = irq;

    dev->refresh = 1;
    
    //kill_fasync(&dev->fa, SIGIO, POLL_IN);
    wake_up(&dev->q);
    
    return IRQ_HANDLED;
}


void key_timer(unsigned long args)
{
    struct s3c2440_key *dev = (struct s3c2440_key *)args;
    dev->interval = 0;
}

int init_s3c2440_key(struct s3c2440_key *dev)
{
    int ret = 0;
    int i;
    dev->irq[0] = IRQ_EINT0;
    dev->irq[1] = IRQ_EINT1;
    dev->irq[2] = IRQ_EINT2;
    dev->irq[3] = IRQ_EINT3;
    dev->irq[4] = IRQ_EINT8;
    dev->irq[5] = IRQ_EINT9;

    dev->GPIO_phys = 0x56000000;

    dev->GPIO_virt = ioremap(dev->GPIO_phys, SZ_4K);
    dev->GPFCON = (*(volatile unsigned long *)(dev->GPIO_virt + 0x50));
    dev->GPFDAT = (*(volatile unsigned long *)(dev->GPIO_virt + 0x54));
    dev->GPFUP = (*(volatile unsigned long *)(dev->GPIO_virt + 0x58));
    dev->GPGCON = (*(volatile unsigned long *)(dev->GPIO_virt + 0x60));
    dev->GPGDAT = (*(volatile unsigned long *)(dev->GPIO_virt + 0x64));
    dev->GPGUP = (*(volatile unsigned long *)(dev->GPIO_virt + 0x68));

    dev->GPFCON &= ~0xAA;
    dev->GPFCON |= 0xAA;
    dev->GPFUP |= 0xF;

    dev->GPGCON &= ~0xA;
    dev->GPGCON |= 0xA;
    dev->GPGUP |= 0x3;

    dev->data = 0;
    dev->refresh = 0;

    dev->interval = 0;


    dev->last_irq = 0;

    dev->read_count = 0;

    dev->timer.function = key_timer;
    dev->timer.data = (unsigned long)dev;

    init_timer(&dev->timer);
    //add_timer(&dev->timer);

    for (i = 0; i < KEY_IRQ_COUNT; i++)
    {
        if (i < 2)
        {
            ret = request_irq(dev->irq[i], key_handler_single, IRQF_TRIGGER_RISING | IRQF_SHARED, "s3c2440 EINT", dev); 
    
        }
        else
        {
            ret = request_irq(dev->irq[i], key_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED, "s3c2440 EINT", dev); 
    
        }    

        if (ret)
        {
            printk("request_irq EINT%d failed!!\n", i);
            return ret;
        }
    }   
    //printk("irq\n");

    return 0;

}

void destory_s3c2440_key(struct s3c2440_key *dev)
{
    int i;
    for (i = 0; i < KEY_IRQ_COUNT; i++)
    {
        free_irq(dev->irq[i], dev);
    }
    del_timer(&dev->timer);
    iounmap(dev->GPIO_virt);
}

int key_open(struct inode *nod, struct file *filp)
{
/*
    struct s3c2440_key *dev;
    dev = container_of(nod->i_cdev, struct s3c2440_key, cdev);
    filp->private_data = (struct s3c2440_key *)dev;
    printk("%p\n", filp->private_data);
*/
    return 0;
}

int key_fasync(int fd, struct file *filp, int on)
{
    fasync_helper(fd, filp, on, &key.fa);
    return 0;
}



int key_release(struct inode *nod, struct file *filp)
{
    key_fasync(-1, filp, 0);
    return 0;
}

ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
/*
    struct s3c2440_key *dev = filp->private_data;
    if (wait_event_interruptible(dev->q, dev->refresh == 1)) return -ERESTARTSYS;
    copy_to_user(buf, &dev->data, 4);
    dev->refresh = 0;
*/
//    down(&key.sem);
    if (wait_event_interruptible(key.q, (key.refresh == 1 ))) return -ERESTARTSYS;
    copy_to_user(buf, &key.data, sizeof(key.data));
    if (2 == key.read_count)
        key.read_count = 0;
    key.read_count++;
    key.refresh = 0;
  //  up(&key.sem);
    return 0;
}

int key_ioctl(struct inode *nod, struct file *filp, unsigned int cmd, unsigned long args)
{
    //struct s3c2440_key *key = container_of(nod->i_cdev, struct s3c2440_key, key_cdev);
    return 0;

}

unsigned int key_poll(struct file *filp, struct poll_table_struct *table)
{

    return 0;
}

struct file_operations key_ops = {
    .open = key_open,
    .release = key_release,
    .read = key_read,
    .ioctl = key_ioctl,
    .poll = key_poll,
    .fasync = key_fasync,
};

struct miscdevice misc_key = {
    .name = "kyo_key",
    .fops = &key_ops,
};


static int key_probe(struct platform_device *pdev)
{

    printk("s3c2440 key device!!\n");

    //init_input(&key);
    
    init_MUTEX(&key.sem);

    misc_register(&misc_key);
    //printk("msic register \n");

    init_waitqueue_head(&key.q);

    init_s3c2440_key(&key);

    return 0;
}

static int key_remove(struct platform_device *pdev)
{
    destory_s3c2440_key(&key);
   // input_unregister_device(key.dev);
    misc_deregister(&misc_key);
    return 0;
}


static struct platform_driver key_driver = {
    .probe = key_probe,
    .remove = key_remove,
    .driver = {
        .name = "s3c2440_key",
        .owner = THIS_MODULE,
    },
};

static int __init s3c2440_key_init(void)
{
    return platform_driver_register(&key_driver); 
}

static void __exit s3c2440_key_exit(void)
{
    platform_driver_unregister(&key_driver);
}

module_init(s3c2440_key_init);
module_exit(s3c2440_key_exit);

MODULE_AUTHOR("kyo");
MODULE_DESCRIPTION("This is Key driver!!");
MODULE_LICENSE("GPL");
